package org.spiderflow.model;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.spiderflow.common.context.SpiderContext;
import org.spiderflow.common.model.SpiderLog;
import org.spiderflow.common.model.SpiderOutput;
import org.spiderflow.core.serializer.FastJsonSerializer;
import org.spiderflow.common.enums.PauseEvent;

import javax.websocket.Session;
import java.io.IOException;
import java.util.Date;

/**
 * WebSocket通讯中爬虫的上下文域
 *
 * @author Administrator
 */
public class SpiderWebSocketContext extends SpiderContext {

    private final Session session;

    private boolean debug;

    private final Object lock = new Object();

    public SpiderWebSocketContext(Session session) {
        this.session = session;
    }

    public boolean isDebug() {
        return debug;
    }

    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    @Override
    public void addOutput(SpiderOutput output) {
        this.write(new WebSocketEvent<>("output", output));
    }

    public void log(SpiderLog log) {
        write(new WebSocketEvent<>("log", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS"), log));
    }

    public <T> void write(WebSocketEvent<T> event) {
        try {
            String message = JSON.toJSONString(event, FastJsonSerializer.getSerializeConfig());
            if (session.isOpen()) {
                synchronized (session) {
                    session.getBasicRemote().sendText(message);
                }
            }
        } catch (RuntimeException | IOException ignored) {
            //do nothing
        }
    }

    @Override
    public void pause(String nodeId, PauseEvent event, String key, Object value) {
        if (!this.debug || !this.isRunning()) {
            return;
        }
        synchronized (this) {
            while (this.debug && this.isRunning()) {
                try {
                    write(new WebSocketEvent<>("debug", new DebugInfo(nodeId, event.eventName(), key, value)));
                    this.wait();
                } catch (InterruptedException ignored) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    @Override
    public void resume() {
        if (!this.debug) {
            return;
        }
        synchronized (lock) {
            lock.notifyAll();
        }
    }

    @Override
    public void stop() {
        if (!this.debug) {
            return;
        }
        synchronized (lock) {
            this.setRunning(false);
            lock.notifyAll();
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof SpiderWebSocketContext)) {
            return false;
        }
        if (!super.equals(o)) {
            return false;
        }
        SpiderWebSocketContext that = (SpiderWebSocketContext) o;
        return session.equals(that.session);
    }

    @Override
    public int hashCode() {
        int result = super.hashCode();
        result = 31 * result + session.hashCode();
        return result;
    }

    static class DebugInfo {

        private String nodeId;

        private String event;

        private String key;

        private Object value;

        public DebugInfo(String nodeId, String event, String key, Object value) {
            this.nodeId = nodeId;
            this.event = event;
            this.key = key;
            this.value = value;
        }

        public String getNodeId() {
            return nodeId;
        }

        public void setNodeId(String nodeId) {
            this.nodeId = nodeId;
        }

        public String getEvent() {
            return event;
        }

        public void setEvent(String event) {
            this.event = event;
        }

        public String getKey() {
            return key;
        }

        public void setKey(String key) {
            this.key = key;
        }

        public Object getValue() {
            return value;
        }

        public void setValue(Object value) {
            this.value = value;
        }
    }
}
